7.1 整型
可移植性技巧:对不超过 32767 的整数采用int或short int,其它情况下使用long int。
注意:不要部分差别地使用长整型,因为长整型操作比较耗时。
6种整数类型 | 说明 | bit(16位机器) | bit(32位机器) | bit(64位机器) | 数值范围(16位机器) | 数值范围(32位机器) | 数值范围(64位机器) | 有无符号 |
---|---|---|---|---|---|---|---|---|
short [signed] [int] |
短整型 | 16 | 16 | 16 | -2^15 ~ 2^15-1 | -2^15 ~ 2^15-1 | -2^15 ~ 2^15-1 | 有 |
short usigned [int] |
无符号短整型 | 16 | 16 | 16 | 0 ~ 2^16-1 | 0 ~ 2^16-1 | 0 ~ 2^16-1 | 无 |
[signed] int |
整型 | 16 | 32 | 32 | -2^15 ~ 2^15-1 | -2^31 ~ 2^31-1 | -2^31 ~ 2^31-1 | 有 |
unsigned int |
无符号整型 | 16 | 32 | 32 | 0 ~ 2^16-1 | 0 ~ 2^32-1 | 0 ~ 2^32-1 | 无 |
long [signed] [int] |
长整型 | 32 | 32 | 64 | -2^31 ~ 2^31-1 | -2^31 ~ 2^31-1 | -2^63 ~ 2^63-1 | 有 |
long unsigned [int] |
无符号长整型 | 32 | 32 | 64 | 0 ~ 2^32-1 | 0 ~ 2^32-1 | 0 ~ 2^64-1 | 无 |
7.1.1 整型常量
进制
注意:
- 整数都是以二进制形式存储的,不会考虑实际的书写方式
- 任何时候都可以从一种书写方式切换到另一种,甚至可以混合使用
进制 | 包含数字 | 写法 | 举例 | 备注 |
---|---|---|---|---|
十进制 | 0~9 | 不能以0开头 | 15 255 | |
八进制 | 0~7 | 必须以0开头 | 017 0377 | |
十六进制 | 0~9和字母a~f | 字母部分大小写均可 | 0xf |
长整型和无符号整型
注意:默认情况下编译器对处于int类型取值范围内的整数使用int类型,否则使用long int。
后缀 | 含义 |
---|---|
U(u) | 指定常量为无符号整型 |
L(l) | 指定常量为长整型 |
7.1.2 读/写整数
溢出:当程序付给变量的值太大以至于无法存储在int类型中时,这时值会被处理为不符合预期的值。
printf
函数和scanf
函数
有符号整数
转换说明符 | 类型 | 进制 |
---|---|---|
%d | int | 十进制 |
%hd | short signed int | 十进制 |
%ld | long signed int | 十进制 |
1 | short int s; |
无符号整数
转换说明符 | 类型 | 进制 |
---|---|---|
%u | unsigned int | 十进制数字 |
%o | unsigned int | 八进制 |
%x | unsigned int | 十六进制 |
1 | unsigned int u; |
7.1.3 程序:数列求和(改进版)
1 | /** |
7.2 浮点数
IEEE浮点标准:
类型 | bit | 最小正数 | 最大值 | 精度 |
---|---|---|---|---|
float | 32 | 1.17x10^-38 | 3.40x10^38 | 6个数字 |
double | 64 | 2.22x0^-308 | 1.79x10^308 | 15个数字 |
long float | >=43 | 未说明 | 未说明 | 未说明 |
long double | >=79 | 未说明 | 未说明 | 未说明 |
c语言浮点数:
浮点数 | 说明 | 适用 |
---|---|---|
float | 单精度浮点数 | 当精度要求不严格时,float类型是很适合的类型 |
double | 双精度浮点数 | 提供更高的精度,适合绝大多数 |
long double | 扩展双精度浮点数 | 支持极高精度的要求,很少会用到 |
注意:
- long double类型没有出现在IEEE标准中,其长度随机器的不同而变化,最常通用的尺寸是80位和128位
7.2.1 浮点常量
语法:必须包含小数点或指数
举个栗子:57.0的多种写法
57.0
57.
57.0e0
57E0
5.7e1
5.7e+1
.57e2
570.e-1
存储方式:
情景 | 说明 |
---|---|
默认情况 | double |
浮点常量F(f) |
float |
浮点常量L(l) |
long double |
注意:double
类型的值在使用时,如果需要会自动转换为float
类型的值。
7.2.2 读/些浮点数
浮点类型 | 读 | 写 |
---|---|---|
float | %e %f %g |
%e %f %g |
double | %e %f %g |
%le %lf %lg |
long double | %Le %Lf %Lg |
%Le %Lf %Lg |
1 | double d; |
7.3 字符型
char:Q&A
char
类型的值可以根据计算机的不同而不同,因为不同的机器可能会有不同的字符集。
ASCII字符集:用7位代码表示128个字符,一些计算机把ASCII码扩展为8位代码以便可以表示256个字符。
字符常量:字符常量需要用单引号括起来,而不是双引号。
将char
当作整数:c语言会按小整数的方式处理字符,毕竟所有字符都是以二进制的形式进行编码的。在ASCII码中,字符的取值范围是0000000(0)~1111111(127)。
优点:利用字符和数相同的属性灵活处理字符,例如
for(ch = 'A'; ch <= 'Z'; ch++) ...
缺点:
- 导致编译器无法检查出的多种编程错误
- 编写出诸如
'a' * 'b' / 'c'
这类无意义的表达式- 妨碍程序的可移植性(因为程序可能会基于一些对字符集的假设)
有符号和无符号
编译器行为:一些编译器按照有符号数据(-128~127)处理
char
,另一些则为无符号(0~255)char
。设置有些编译器允许程序员通过编译器选项选择char
时有符号型还是无符号型
可移植性技巧:用signed char
或unsigned char
代替char
。
1 | char ch; |
7.3.1 转义序列
用途:c语言为了处理字符集中的每一个字符,提供了转义字符
用来表示一些不可见或无法从键盘输入的字符。
字符转义字符:没有包含所有无法打印的ASCII字符,只包含了最常用的字符。
名称 | 转移序列 |
---|---|
警报(响铃)符 | \a |
回退符 | \b |
换页符 | \f |
换行符 | \n |
回车符 | \r |
横向制表符 | \t |
纵向制表符 | \v |
反斜杠 | \\ |
问号 | \? |
单引号 | \' |
双引号 | \" |
数字转义字符:可以表示任何字符,突破字符转义字符
的限制。
转义字符常量:有8进制和16进制两种书写方式,需要用一对单引号括起来。
进制 | 书写方式 | 特点 |
---|---|---|
八进制转义序列 | \最多含有三位数字的八进制数 |
必须表示为无符号字符型 |
十六进制转义序列 | \x十六进制数 |
标准c对于数字个数没有限制,x 必须为小写,十六进制数无限制 |
1 | # define ESC '\33' //用宏的方式定义数字转义字符常量 |
补充:
其它序列 | 说明 |
---|---|
三字符序列(trigraph sequence) | 一些特殊的ASCII字符的代码 |
多字节字符(multibyte character) | |
宽字符(nultibyte character) |
7.3.2 字符处理函数
toupper:c语言的toupper库函数,用来检测自身的参数是否是小写字母,如果是则将其转换成相应的大写字母。
1 |
|
7.3.3 读/写字符
两种方式:printf
/scanf
和getChar
/putchar
7.3.3.1 printf 和 scanf
转换说明:%c
技巧:
- 在格式串转换说明
%c
前面加一个空格,强制scanf
函数在读入字符前跳过空白字符。
1 | scanf(" %c", &ch);//会跳过零个或多个空白字符 |
- 通常情况下scanf函数不会跳过空白,所以很容易检查到输入行的结尾
1 | do{ |
1 | char ch; |
7.3.3.2 getchar 和 putchar
说明:
库方法 | 说明 | 备注 |
---|---|---|
getchar | 读入一个字符并返回这个字符 | 不会跳过空白字符 |
putchar | 用来单独写一个字符 |
执行速度快:比 scanf 和 printf 节约时间。
- 这两个函数比scanf和prinf简单,因为scanf和prinf是设计用来读/写多种不同格式类型数据的
- 为了额外的速度提升,通常getchar函数和puchar函数是作为宏来实现的
可以应用多种不同的c语言惯用法:包括用循环搜索字符或跳过所有出现的统一字符。
1 | //循环搜索字符 |
避免混合使用scanf
和getchar
:scanf函数有一种留下后边字符的趋势,也就是说对于输入后面的字符只是看了一下,并没有读入。
1 | printf("Enter an interger:"); |
7.3.4 程序:确定消息的长度
1 | /** |
1 | ./length2 |
7.4 sizeof运算符
说明:编译器本身就可以计算sizeof表达式的值,所以sizeof 是一种特殊的运算符(一元运算符)
用途:用来计算制定类型值所需空间的大小。
sizeof表达式:sizeof(类型名)
参数:常量、变量或表达式
优先级:高于二元运算符(比如+)
返回值:字节数,类型由实现定义,通常是无符号整数
圆括号:当应用于表示式时不需要圆括号,比如sizeof i
,但是由于运算符优先级的问题,建议始终在sizeof表达式中采用圆括号。
以int
为例:
处理器架构 | 字节数 |
---|---|
16 | 2(通常) |
32 | 4(通常) |
1 | //把sizeof表达式转换成`unsigned long`(最大的无符号类型) |
7.5 类型转换
计算机执行算术运算的限制:要求操作数大小相同,存储方式也相同。
7.5.1 隐式转换(implicit conversion)
说明:c语言允许在表达式中混合使用基本数据类型(整数、浮点数甚至是字符)。此时c语言编译器需要生成一些指令将某些操作数转换成不同类型,是的硬件可以对表达式进行计算。
隐式转换情景:
- 当算术表达式或逻辑表达式中操作数的类型不相同时(常用算数转换)
- 当赋值运算符右侧表达式的类型和左侧变量的类型不匹配时
- 当函数调用中使用的参数类型与其对应的参数的类型不匹配时
- 当 return语句中表达式的类型和函数返回值的类型不匹配时
7.5.1 常用算数转换
原理:为了统一操作数的类型,通常可以将相对狭小的操作数转换成另一个操作数的类型来实现(即提升)。
整型提升(integral promition):把char
或short int
转换成int
(或unsigned int
)。
两种情况:
情况 | 提升方式 |
---|---|
任一操作数的类型时浮点型的情况 | long double<-double<-float |
两个操作数都不是浮点型的情况 | unsigned long int<-long int<-usigned int<-int |
long int 和unsigned int |
两个操作数都会转换成usigned long int |
注意:尽量避免使用unsigned int
,特别不要把它和有符号整数混合使用。
1 | int i ,u; |
1 | char c; |
7.5.2 赋值中的转换
转化规则:把赋值运算右边的表达式转换成左边变量的类型。
7.5.2.1 两边类型一样“宽”
1 | char c; |
7.5.2.2 浮点数赋值给整型
规则:去掉小数部分。
1 | int i; |
7.5.2.3 溢出
说明:Q&A
如果取值在变量类型范围之外,那么把值赋给一个狭小类型的变量将会得到无意义的结果(设置更糟)。
1 | c = 10000; |
7.5.3 强制类型转换
强制转换表达式:(类型名)表达式
优先级:被当作一元运算符,因此高于二元运算符
1 | float f, frac_part; |
7.6 类型定义
语法:typedef 原本类型名 类型别名;
说明:采用typedef定义类型别名会导致编译器将其加入到类型列表中。
用途:
- 如果程序员使用有意义的类型名,会使程序更加易于理解
- 使程序更加易于维护
- 是编写可移植程序的一种重要工具
可移植性技巧:为了更大的可移植性,可以考虑使用typedef
定义新的整型名。
1 | //在16位机器上 |
编译器库中(可能)自带的类型定义:
1 | typedef int ptrdiff_t; |
1 | typedef float Dollars; |